package com.youlai.redis.service;


import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.stream.ObjectRecord;
import org.springframework.data.redis.connection.stream.StreamRecords;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;


/**
 * redis高级特性
 */
@Component
public class AdvancedFeaturesService {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    /**
     * 事务操作
     *
     * @param isOpenError 是否开启异常
     */
    public void transactionalMethod(boolean isOpenError) {
        redisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) {
                operations.multi(); // 开启事务
                ValueOperations<String, String> valueOps = operations.opsForValue();
                valueOps.set("key1", "value1");
                if (isOpenError) {
                    int i = 1 / 0;
                }
                valueOps.set("key2", "value2");
                List exec = operations.exec();//提交事务
                return exec;
            }
        });
    }

    /**
     * 管道操作
     * 注意管道操作不支持事务和watch命令，需要谨慎使用。
     * 管道操作会将多个命令打包成一个请求发送给Redis服务器，
     * 如果其中一个命令执行失败，那么整个管道操作都会失败
     */
    public void pipelineExample() {
        List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.stringCommands().set("key1".getBytes(), "value1".getBytes());
                connection.stringCommands().set("key2".getBytes(), "value2".getBytes());
                connection.stringCommands().set("key3".getBytes(), "value3".getBytes());
                return null;
            }
        });
        System.out.println(results); // 打印结果
    }


    /**
     * 发布消息
     *
     * @param message
     */
    public void publish(String message) {
        redisTemplate.convertAndSend("pubsub:example", message);
    }

    /**
     * 发布消息 不同于发布订阅的是 同一个消费者组中只能有一个消费者监听到消息
     *
     * @param message
     */
    public void publishStream(String message) {
        ObjectRecord<String, String> record = StreamRecords.newRecord()
                .ofObject(message)
                .withStreamKey("exampleStreamKey");
        // 发送消息
        redisTemplate.opsForStream().add(record);
    }

    /**
     * 使用 Lua 脚本实现 Redis 自增计数器
     *
     * @param key
     * @return
     */
    public Long increment(String key) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/increment.lua")));
        script.setResultType(Long.class);
        List<String> keys = Collections.singletonList(key);
        return redisTemplate.execute(script, keys);
    }

    /**
     * 过期时间
     */
    public void expirationTime() {
        //设置过期时间
        redisTemplate.expire("key", 60, TimeUnit.SECONDS);  //对任意类型的键设置过期时间
        //设置过期时间
        redisTemplate.opsForValue().set("key", "value", 60, TimeUnit.SECONDS); //只能对字符串类型的键设置过期时间
        //获取剩余过期时间
        Long aLong = redisTemplate.getExpire("key");
        System.out.println("剩余过期时间:" + aLong);
    }

}
